#include "structure/Boom.h"
#include "Constants.h"
#include "TextureMgr.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/transform2.hpp>
#include <glm/gtx/projection.hpp>
#include <cmath>
#include "InputMgr.h"

const char Boom::boomUp = KEY_BOOM_ROTATE_UP;
const char Boom::boomDown = KEY_BOOM_ROTATE_DOWN;
const char Boom::cableUp = KEY_CABLE_UP;
const char Boom::cableDown = KEY_CABLE_DOWN;

Boom::Boom(GLuint programHandler, GLShaderConfig shaderConfig, float sizeBottom, float sizeTop, float length) :
	GLStructure(programHandler, shaderConfig),
	sizeBottom(sizeBottom),
	sizeTop(sizeTop),
	length(length),
	cableHeight(CABLE_MIN_HEIGHT),
	hookHeight(HOOK_HEIGHT),
	boomRotationAngle(0) {
	column1 = new Cube(programHandler, shaderConfig);
	column2 = new Cube(programHandler, shaderConfig);
	column3 = new Cube(programHandler, shaderConfig);
	column4 = new Cube(programHandler, shaderConfig);

	frame1 = new Frame(programHandler, shaderConfig, 1.0);
	frame2 = new Frame(programHandler, shaderConfig, 1.0);
	frame3 = new Frame(programHandler, shaderConfig, 1.0);
	frame4 = new Frame(programHandler, shaderConfig, 1.0);

	cable = new ClosedCylinder(programHandler, shaderConfig, CABLE_WIDTH, cableHeight);
	hook = new ClosedCylinder(programHandler, shaderConfig, CABLE_WIDTH * 2, hookHeight);

	components.push_back(column1);
	components.push_back(column2);
	components.push_back(column3);
	components.push_back(column4);

	structures.push_back(frame1);
	structures.push_back(frame2);
	structures.push_back(frame3);
	structures.push_back(frame4);
	structures.push_back(cable);
	structures.push_back(hook);

	column1->setTextureData(TextureMgr::getInstance()->getBoomColumnDiffuseTextureData(), TextureMgr::getInstance()->getBoomColumnDirtTextureData());
	column2->setTextureData(TextureMgr::getInstance()->getBoomColumnDiffuseTextureData(), TextureMgr::getInstance()->getBoomColumnDirtTextureData());
	column3->setTextureData(TextureMgr::getInstance()->getBoomColumnDiffuseTextureData(), TextureMgr::getInstance()->getBoomColumnDirtTextureData());
	column4->setTextureData(TextureMgr::getInstance()->getBoomColumnDiffuseTextureData(), TextureMgr::getInstance()->getBoomColumnDirtTextureData());

	cable->setTextureData(TextureMgr::getInstance()->getCableDiffuseTextureData(), TextureMgr::getInstance()->getCableDirtTextureData());
	hook->setColor(0.6, 0.6, 0.6);

	InputMgr::getInstance()->registerListener(this);
}

Boom::~Boom() {
	components.clear();
	structures.clear();

	delete column1;
	delete column2;
	delete column3;
	delete column4;

	delete frame1;
	delete frame2;
	delete frame3;
	delete frame4;

	delete cable;
	delete hook;
}

void Boom::updateComponents() {

	glm::mat4 commonTransform = glm::rotate(transformationMatrix, boomRotationAngle, glm::vec3(1, 0, 0));

	//    ______        z          ______
	//   |      |       ^         |      |
	//   | col1 |       |         | col2 |
	//   |______|       |         |______|
	//                  |
	// x <-----------(0,0,0)
	//
	//    ______                   ______
	//   |      |                 |      |
	//   | col3 |                 | col4 |
	//   |______|                 |______|

	float diff = (sizeBottom - sizeTop) / 2;
	float positiveDiff = (diff > 0 ? diff : -diff);
	float angle = atanf(positiveDiff / length) * 180 / PI;
	float columnWidth = 0.2f;
	float diagonalLength = sqrtf(powf(positiveDiff, 2.0) + powf(length, 2.0));
	glm::mat4 columnTransform = glm::translate(commonTransform, glm::vec3(sizeBottom/2, 0, sizeBottom/2));
	columnTransform = glm::rotate(columnTransform, angle, glm::vec3(0, 0, 1.0f));
	columnTransform = glm::rotate(columnTransform, angle, glm::vec3(-1.0f, 0, 0));
	columnTransform = glm::translate(columnTransform, glm::vec3(0, diagonalLength/2, 0));
	columnTransform = glm::rotate(columnTransform, 45.0f, glm::vec3(0, 1, 0));
	columnTransform = glm::scale(columnTransform, glm::vec3(columnWidth, diagonalLength, columnWidth));
	column1->transform(columnTransform);

	columnTransform = glm::translate(commonTransform, glm::vec3(-sizeBottom/2, 0, sizeBottom/2));
	columnTransform = glm::rotate(columnTransform, angle, glm::vec3(-1.0f, 0, 0));
	columnTransform = glm::rotate(columnTransform, angle, glm::vec3(0, 0, -1.0f));
	columnTransform = glm::translate(columnTransform, glm::vec3(0, diagonalLength/2, 0));
	columnTransform = glm::rotate(columnTransform, 45.0f, glm::vec3(0, 1, 0));
	columnTransform = glm::scale(columnTransform, glm::vec3(columnWidth, diagonalLength, columnWidth));
	column2->transform(columnTransform);

	columnTransform = glm::translate(commonTransform, glm::vec3(sizeBottom/2, 0, -sizeBottom/2));
	columnTransform = glm::rotate(columnTransform, angle, glm::vec3(1.0f, 0, 0));
	columnTransform = glm::rotate(columnTransform, angle, glm::vec3(0, 0, 1.0f));
	columnTransform = glm::translate(columnTransform, glm::vec3(0, diagonalLength/2, 0));
	columnTransform = glm::rotate(columnTransform, 45.0f, glm::vec3(0, 1, 0));
	columnTransform = glm::scale(columnTransform, glm::vec3(columnWidth, diagonalLength, columnWidth));
	column3->transform(columnTransform);

	columnTransform = glm::translate(commonTransform, glm::vec3(-sizeBottom/2, 0, -sizeBottom/2));
	columnTransform = glm::rotate(columnTransform, angle, glm::vec3(0, 0, -1.0f));
	columnTransform = glm::rotate(columnTransform, angle, glm::vec3(1.0f, 0, 0));
	columnTransform = glm::translate(columnTransform, glm::vec3(0, diagonalLength/2, 0));
	columnTransform = glm::rotate(columnTransform, 45.0f, glm::vec3(0, 1, 0));
	columnTransform = glm::scale(columnTransform, glm::vec3(columnWidth, diagonalLength, columnWidth));
	column4->transform(columnTransform);

	float pos = length;
	float frameSize = sizeTop;
	frame1->setSize(frameSize);
	glm::mat4 commonFrameTransform = glm::rotate(commonTransform, 90.0f, glm::vec3(0, 0, 1));
	glm::mat4 frameTransform = glm::translate(commonFrameTransform, glm::vec3(pos, 0, 0));
	frame1->transform(frameTransform);

	pos = length * 3 / 4;
	frameSize = sizeBottom - 2 * (pos * positiveDiff / length);
	frame2->setSize(frameSize);
	frameTransform = glm::translate(commonFrameTransform, glm::vec3(pos, 0, 0));
	frame2->transform(frameTransform);

	pos = length / 2;
	frameSize = sizeBottom - 2 * (pos * positiveDiff / length);
	frame3->setSize(frameSize);
	frameTransform = glm::translate(commonFrameTransform, glm::vec3(pos, 0, 0));
	frame3->transform(frameTransform);

	pos = length / 4;
	frameSize = sizeBottom - 2 * (pos * positiveDiff / length);
	frame4->setSize(frameSize);
	frameTransform = glm::translate(commonFrameTransform, glm::vec3(pos, 0, 0));
	frame4->transform(frameTransform);


	glm::mat4 commonCableTransform = glm::translate(commonTransform, glm::vec3(0, length, -sizeTop/2));
	commonCableTransform = glm::rotate(commonCableTransform, -boomRotationAngle, glm::vec3(1, 0, 0));

	glm::mat4 cableTransform = glm::translate(commonCableTransform, glm::vec3(0, 0, - cableHeight / 2));
	cableTransform = glm::scale(cableTransform, glm::vec3(1, 1, cableHeight));
	cable->transform(cableTransform);

	glm::mat4 hookTransform = glm::translate(commonCableTransform, glm::vec3(0, 0, - cableHeight - hookHeight / 2));
	hook->transform(hookTransform);
}

void Boom::update (glm::mat4 viewMatrix, glm::mat4 modelMatrix) {
	updateComponents();
	TextureMgr::getInstance()->loadTextures(programHandler);
	std::list<GLObject*>::iterator itc = components.begin();
	for ( ; itc != components.end(); itc++ ) {
		(*itc)->update(viewMatrix, modelMatrix);
	}
	std::list<GLStructure*>::iterator its = structures.begin();
	for ( ; its != structures.end(); its++ ) {
		(*its)->update(viewMatrix, modelMatrix);
	}
}

void Boom::OnKeyDown(int nKey, char cAscii) {
	if (cAscii == boomUp) {
		boomRotationAngle += BOOM_SPEED;
		if (boomRotationAngle > BOOM_MAX_ANGLE) {
			boomRotationAngle = BOOM_MAX_ANGLE;
		}
	} else if (cAscii == boomDown) {
		boomRotationAngle -= BOOM_SPEED;
		if (boomRotationAngle < 0) {
			boomRotationAngle = 0;
		}
	} else if (cAscii == cableUp) {
		cableHeight -= CABLE_SPEED;
		if (cableHeight < CABLE_MIN_HEIGHT) {
			cableHeight = CABLE_MIN_HEIGHT;
		}
	} else if (cAscii == cableDown) {
		cableHeight += CABLE_SPEED;
		if (cableHeight > CABLE_MAX_HEIGHT) {
			cableHeight = CABLE_MAX_HEIGHT;
		}
	}
}
